

package commandz.cli;

import commandz.var.Variable;
import java.util.*;


/**
 *  Parses and executes commands.
 */
public class Interpreter
{
    /*
     *  Associative storage for the available commands.
     */
    protected Map<String, Command> commands;

    protected InterpreterFeedback feedback;


    /**
     *  Initializes a new instance of the Interpreter class.
     *
     *  Interpretation errors will be reported to the given
     *  InterpreterFeedback-object.
     *
     *  @param fb An object that will receive interpretation errors.
     */
    public Interpreter(final InterpreterFeedback fb)
    {
        this.commands = new TreeMap<String, Command>();
        this.feedback = fb;
    }


    /**
     *  Initializes a new instance of the Interpreter class.
     *
     *  Interpretation errors will be sent into the void.
     */
    public Interpreter()
    {
        this(new InterpreterFeedbackAdapter());
    }


    /**
     *  Lets the interpreter recognize the given command.
     *
     *  @param cmd A Command-object to recognize.
     */
    public void recognize(final Command cmd)
    {
        this.commands.put(cmd.getName(), cmd);
    }


    /**
     *  Makes an alias for the given identifier.
     *
     *  @param identifier The name of a existing command.
     *  @param pseudonym  The pseudonym to use.
     */
    public void recognize(final String identifier, final String pseudonym)
    {
        Command cmd = lookup(identifier);

        if (cmd == null)  // not found?
        {
            this.feedback.unknownCommand(this, identifier);
            return;
        }

        recognize(new Alias(pseudonym, cmd));
    }


    /**
     *  Makes the interpreter forget about the given command.
     *
     *  @param cmd A Command-object to forget.
     */
    public void forget(final Command cmd)
    {
        this.commands.remove(cmd.getName());
    }


    /**
     *  Removes all known commands from the interpreter.
     */
    public void clear()
    {
        this.commands.clear();
    }


    /**
     *  Returns the requested command or null if not available.
     *
     *  @param identifier The name that identifies a Command.
     *
     *  @return The requested Command or null.
     */
    public Command lookup(final String identifier)
    {
        return this.commands.get(identifier);
    }


    /**
     *  Returns a collection of commands known by this interpreter.
     *
     *  @return A Collection of Command-objects.
     */
    public Collection<Command> getCommands()
    {
        return this.commands.values();
    }

    
    /**
     *  Interprets the given line of text.
     *
     *  @param line A line of text ready for interpretation.
     *  
     *  TODO: Improve the crappy tokenization.
     */
    public void interpret(final String line)
    {
        String tokens[] = line.split("\\s+");
        
        if (tokens.length == 0)  // only emptyness?
            return;

        String first = tokens[0];

        if (first.isEmpty() || first.charAt(0) == '#')
            return;

        interpret(tokens);
    }


    /**
     *  Interprets the given sequence of tokens.
     *
     *  @param tokens An array of tokens ready for interpretation.
     */
    public void interpret(String tokens[])
    {
        Command cmd = lookup(tokens[0]);

        if (cmd == null)    // not found?
        {
            this.feedback.unknownCommand(this, tokens[0]);
            return;
        }

        Collection<Parameter> params = new ArrayList<Parameter>();
        cmd.publishParameters(params);

        int args = tokens.length - 1;
        int pars = params.size();

        if (args != pars)  // argumentation mismatch?
        {
            this.feedback.argumentMismatch(this, cmd, pars, args);
            return;
        }

        boolean argfault = false;
        int i = 0;

        for (Parameter p : params)  // read and initialize each parameter..
        {
            Variable var = p.getVariable();
            String token = tokens[++i];

            if (!var.parse(token))  // parse error?
            {
                argfault = true;
                this.feedback.argumentError(this, cmd, p, token);
            }
        }

        if (argfault)  // sorry, can't execute.
        {
            return;
        }

        cmd.execute();
    }

}
